﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SharedTextEditor
{
  public class BroadCast
  {
    private const int PORT = 15000;

    private bool stopBroadCast;
    private bool stopReceiving;
    private Guid myId;
    private IList<CommunicationPartner> partners;
    private UdpClient udp;

    public BroadCast(IList<CommunicationPartner> partners, Guid myId)
    {
      this.partners = partners;

      udp = new UdpClient();
      udp.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); // for binding more then one client to port
      udp.Client.Bind(new IPEndPoint(IPAddress.Any, PORT));

      this.myId = myId;
    }

    public string DocumentTitle { get; set; }

    public string Address { get; set; }

    public event EventHandler NewPartnerFound;

    public void RequestStopBroadCast()
    {
      stopBroadCast = true;
    }


    //Inital synchron partner search
    //Waits a few seconds for partner broadcasts
    public void InitialPartnerSearchSync()
    {
      udp.Client.ReceiveTimeout = 500;
      Stopwatch watch = new Stopwatch();
      watch.Start();
      while (watch.ElapsedMilliseconds < 2000)
      {
        try
        {
          IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, PORT);
          byte[] bytes = this.udp.Receive(ref endpoint);
          HandleResponse(endpoint, bytes);
        }
        catch(SocketException e)
        {
          Console.WriteLine(e);
        }
      }
    }
    
    public void StartFindingPartnersAsync()
    {
      stopReceiving = false;
      udp.BeginReceive(ReceiveBroadCast, new object());
    }

    public void StopFindingPartnersAsync()
    {
      stopReceiving = true;
    }

    private void HandleResponse(IPEndPoint endpoint,byte[] bytes)
    {
      FindMessage msg = ByteArrayToFindMessage(bytes);

      //new partner
      if (msg != null && partners.FirstOrDefault(p => p.Id == msg.Id) == null && msg.Id != myId && msg.DocumentTitle == DocumentTitle)
      {
        var comPartner = new CommunicationPartner();
        comPartner.Id = msg.Id;
        comPartner.Address = msg.Address;
        partners.Add(comPartner);
        if (NewPartnerFound != null)
          NewPartnerFound(this, new EventArgs());
      }
    }

    private void ReceiveBroadCast(IAsyncResult ar)
    {
      IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, PORT);
      byte[] bytes = udp.EndReceive(ar, ref endpoint);
      if (!stopReceiving)
      {
        HandleResponse(endpoint, bytes);
        StartFindingPartnersAsync();
      }
    }

    public void StartSendingPartnerBroadCast()
    {
      stopBroadCast = false;
      while (!stopBroadCast)
      {
        SendBroadCast();
        Thread.Sleep(1000);
      }
    }

    private void SendBroadCast()
    {
      UdpClient client = new UdpClient();
      IPEndPoint ip = new IPEndPoint(IPAddress.Broadcast, PORT);
      FindMessage msg = new FindMessage() { Id = myId, DocumentTitle = DocumentTitle, Address = Address };
      byte[] bytes = FindMessageToByteArray(msg);
      client.Send(bytes, bytes.Length, ip);
      client.Close();
    }

    // Convert an FindMessag to a byte array
    private byte[] FindMessageToByteArray(FindMessage obj)
    {
      if (obj == null)
        return null;
      BinaryFormatter bf = new BinaryFormatter();
      MemoryStream ms = new MemoryStream();
      bf.Serialize(ms, obj);
      return ms.ToArray();
    }

    // Convert a byte array to FindMessage
    private FindMessage ByteArrayToFindMessage(byte[] arrBytes)
    {
      MemoryStream memStream = new MemoryStream();
      BinaryFormatter binForm = new BinaryFormatter();
      memStream.Write(arrBytes, 0, arrBytes.Length);
      memStream.Seek(0, SeekOrigin.Begin);
      FindMessage obj = (FindMessage)binForm.Deserialize(memStream);
      return obj;
    }
  }
}
